<?php

namespace app\common\service;

use app\common\exception\BusinessException;
use app\common\model\AppConfig;
use Exception;

/**
 * One23PanService
 */
class One23PanService
{
    /**
     * host
     * @var string
     */
    private $host = 'https://open-api.123pan.com';

    /**
     * uid
     * @var string
     */
    private $uid = '';

    /**
     * privateKey
     * @var string
     */
    private $privateKey = '';

    /**
     * clientId
     * @var string
     */
    private $clientId = '';

    /**
     * clientSecret
     * @var string
     */
    private $clientSecret = '';

    /**
     * accessToken
     * @var string
     */
    private $accessToken = '';

    public function getUid(): string
    {
        return $this->uid;
    }

    public function setUid(string $uid): void
    {
        $this->uid = $uid;
    }

    public function getPrivateKey(): string
    {
        return $this->privateKey;
    }

    public function setPrivateKey(string $privateKey): void
    {
        $this->privateKey = $privateKey;
    }

    public function getClientId(): string
    {
        return $this->clientId;
    }

    public function setClientId(string $clientId): void
    {
        $this->clientId = $clientId;
    }

    public function getClientSecret(): string
    {
        return $this->clientSecret;
    }

    public function setClientSecret(string $clientSecret): void
    {
        $this->clientSecret = $clientSecret;
    }

    public function getHost(): string
    {
        return $this->host;
    }

    public function setHost(string $host): void
    {
        $this->host = $host;
    }

    public function getAccessToken(): string
    {
        return $this->accessToken;
    }

    public function setAccessToken(string $accessToken): void
    {
        $this->accessToken = $accessToken;
    }

    public function init($appId)
    {
        // 读取配置
        $config = app(AppConfig::class)->getByIdAndType($appId, 'One23Pan');
        $this->setUid($config['uid']);
        $this->setPrivateKey($config['privateKey']);
        $this->setClientId($config['clientId']);
        $this->setClientSecret($config['clientSecret']);
        $this->setAccessToken($config['clientSession']);
    }

    public function initAccessToken()
    {
        $path = '/api/v1/access_token';
        $postData = [
            'clientID' => $this->clientId,
            'clientSecret' => $this->clientSecret,
        ];
        $responseArray = $this->execute($path, [], $postData);
        $accessToken = $responseArray['data']['accessToken'] ?? '';
        $this->setAccessToken($accessToken);
    }

    public function detail($fileID)
    {
        // 01.create
        $path = '/api/v1/file/detail';
        $getData = [
            'fileID' => $fileID,
        ];
        $responseArray = $this->execute($path, $getData, []);
        return $responseArray['data'] ?? [];
    }

    public function trash($fileID)
    {
        // 01.trash
        $path = '/api/v1/file/trash';
        $postData = [
            'fileIDs' => [$fileID],
        ];
        $responseArray = $this->execute($path, [], json_encode($postData));
        return $responseArray['data'] ?? [];
    }

    private function listUploadParts($preuploadID)
    {
        // 01.create
        $path = '/upload/v1/file/list_upload_parts';
        $postData = [
            'preuploadID' => $preuploadID,
        ];
        $responseArray = $this->execute($path, [], $postData);
        return $responseArray['data'] ?? [];
    }

    private function uploadFileSlice($localFilePath, $sliceSize, $preUploadID)
    {
        //open file
        $fileHandle = fopen($localFilePath, 'r');

        // 循环读取文件并分片
        $counter = 0;
        while (!feof($fileHandle)) {
            //读取分片数据
            $chunk = fread($fileHandle, $sliceSize);
            if (!$chunk) {
                throw new BusinessException('读取分片文件失败');
            }

            //增加分片数
            $counter++;

            //获取分片上传地址
            $uploadUrl = $this->getUploadUrl($preUploadID, $counter);
            if (!$uploadUrl) {
                throw new BusinessException('获取分片上传地址失败');
            }

            //PUT上传文件
            $upload = curlPutFile($uploadUrl, $chunk);
            if (!$upload) {
                throw new BusinessException('上传分片内容失败');
            }
        }

        //close file
        fclose($fileHandle);

        // return
        return $counter;
    }

    /**
     * offlineDownload
     * @param $url
     * @param $fileName
     * @param $dirID
     * @param $callBackUrl
     * @return array|mixed
     * @throws Exception
     */
    public function offlineDownload($url, $fileName, $dirID, $callBackUrl = '')
    {
        // 01.create
        $path = '/api/v1/offline/download';
        $postData = [
            'url' => $url,
            'fileName' => $fileName,
            'dirID' => $dirID,
            'callBackUrl' => $callBackUrl,
        ];
        $responseArray = $this->execute($path, [], $postData);
        return $responseArray['data'] ?? [];
    }

    public function upload($parentFileID, $filename, $localFilePath)
    {
        // 01.create( if has file hash,will return fileId )
        $path = '/upload/v1/file/create';
        $postData = [
            'parentFileID' => $parentFileID,
            'filename' => $filename,
            'etag' => md5_file($localFilePath),
            'size' => filesize($localFilePath),
        ];
        $responseArray = $this->execute($path, [], $postData);
        if (!empty($responseArray['data']['fileID'])) {
            return $responseArray['data']['fileID'];
        }

        // 02.slice file by
        $sliceSize = $responseArray['data']['sliceSize'];
        $preUploadID = $responseArray['data']['preuploadID'];
        $count = $this->uploadFileSlice($localFilePath, $sliceSize, $preUploadID);

        // 03.listUploadParts
        $uploadPart = $this->listUploadParts($preUploadID);
        $uploadPartCount = count($uploadPart['parts']);
        if ($count > 1 && $uploadPartCount != $count) {
            throw new BusinessException('数据分片对比不完整');
        }

        // 04.uploadFile
        $responseArray = $this->uploadComplete($preUploadID);
        if (!empty($responseArray['fileID'])) {
            return $responseArray['fileID'];
        }

        // 05.async query result
        $max = 60;
        while ($max--) {
            $responseArray = $this->uploadAsyncResult($preUploadID);
            if (!empty($responseArray['fileID'])) {
                return $responseArray['fileID'];
            }
        }
        throw new BusinessException('上传文件到123云盘失败');
    }

    /**
     * uploadComplete
     * @param $preUploadID
     * @return mixed
     * @throws Exception
     */
    private function uploadComplete($preUploadID)
    {
        $path = '/upload/v1/file/upload_complete';
        $postData = [
            'preuploadID' => $preUploadID,
        ];
        $responseArray = $this->execute($path, [], $postData);
        return $responseArray['data'];
    }

    /**
     * uploadComplete
     * @param $preUploadID
     * @return mixed
     * @throws Exception
     */
    private function uploadAsyncResult($preUploadID)
    {
        $path = '/upload/v1/file/upload_async_result';
        $postData = [
            'preuploadID' => $preUploadID,
        ];
        $responseArray = $this->execute($path, [], $postData);
        return $responseArray['data'];
    }

    public function getUploadUrl($preUploadID, $sliceNo)
    {
        $path = '/upload/v1/file/get_upload_url';
        $postData = [
            'preuploadID' => $preUploadID,
            'sliceNo' => $sliceNo,
        ];
        $responseArray = $this->execute($path, [], $postData);
        return $responseArray['data']['presignedURL'] ?? '';
    }

    /**
     * searchFile
     * @param $keyword
     * @param $filePath
     * @param $page
     * @param $num
     * @return array
     * @throws Exception
     */
    public function searchFile($parentFileId, $searchData, $page = 1, $limit = 50): array
    {
        $path = '/api/v1/file/list';
        $postData = [
            'parentFileId' => $parentFileId,
            'page' => $page,
            'limit' => $limit,
            'orderBy' => 'file_name',
            'orderDirection' => 'asc',
            'searchData' => $searchData,
        ];
        $responseArray = $this->execute($path, $postData, []);
        return $responseArray['data']['fileList'] ?? [];
    }

    /**
     * createDir
     * @param $name
     * @param $parentID
     * @return array
     * @throws Exception
     */
    public function createDir($name, $parentID)
    {
        $path = '/upload/v1/file/mkdir';
        $postData = [
            'name' => $name,
            'parentID' => $parentID,
        ];
        $responseArray = $this->execute($path, [], $postData);
        return $responseArray['data']['dirID'] ?? [];
    }

    /**
     * getFileDetail
     * @param $fileID
     * @return array
     * @throws Exception
     */
    public function getFileDetail($fileID)
    {
        $path = '/api/v1/file/detail';
        $getData = [
            'fileID' => $fileID,
        ];
        $responseArray = $this->execute($path, $getData, []);
        return $responseArray['data'];
    }

    /**
     * rename
     * @param $fileID
     * @param $fileName
     * @return array
     * @throws Exception
     */
    public function rename($fileID, $fileName)
    {
        $path = '/api/v1/file/name';
        $postData = [
            'fileId' => $fileID,
            'fileName' => $fileName,
        ];
        $responseArray = $this->putExecute($path, [], $postData);
        return $responseArray['data'];
    }

    /**
     * putExecute
     * @param $path
     * @param $getData
     * @param $postData
     * @return array
     * @throws Exception
     */
    public function putExecute($path, $getData, $postData)
    {
        // 请求地址
        $api = $this->host . $path . '?' . http_build_query($getData);

        // 请求参数
        $header = ['platform:open_platform'];
        if ($this->accessToken) {
            $header[] = 'authorization:' . $this->accessToken;
        }

        // 发送请求
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $api);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $response = curlExecByRetry($ch, 1, 1);
        if (!$response) {
            throw new Exception('获取响应内容失败');
        }

        // 解析数据
        $responseArray = json_decode($response, true);
        if (!$responseArray) {
            throw new Exception('响应内容非标准json格式');
        }

        // 检查错误信息
        if (isset($responseArray['code']) && $responseArray['code'] !== 0) {
            throw new Exception('请求接口' . $api . '失败,错误码:' . $responseArray['code'] . ',错误信息:' . $responseArray['message']);
        }

        // 返回
        return $responseArray;
    }

    /**
     * execute
     * @param $path
     * @param $getData
     * @param $postData
     * @return array
     * @throws Exception
     */
    public function execute($path, $getData, $postData)
    {
        // 请求地址
        $api = $this->host . $path . '?' . http_build_query($getData);

        // 请求参数
        $header = ['platform:open_platform'];
        if ($this->accessToken) {
            $header[] = 'authorization:' . $this->accessToken;
        }

        // 发送请求
        $response = curl($api, $postData, false, $header, 1, 1);
        if (!$response) {
            throw new Exception('获取响应内容失败');
        }

        // 解析数据
        $responseArray = json_decode($response, true);
        if (!$responseArray) {
            throw new Exception('响应内容非标准json格式');
        }

        // 检查错误信息
        if (isset($responseArray['code']) && $responseArray['code'] !== 0) {
            throw new Exception('请求接口' . $api . '失败,错误码:' . $responseArray['code'] . ',错误信息:' . $responseArray['message']);
        }

        // 返回
        return $responseArray;
    }

    /**
     * buildSignedUrl
     * @param $video
     * @return string
     */
    public function buildSignedUrl($video, $expire_time): string
    {
        if (!empty($video['platform_video_id'])) {
            $response = $this->download($video['platform_video_id']);
            return $response['downloadUrl'];
        }
        $expire_time = time() + $expire_time;
        $rand_value = rand(0, 100000);
        $parse_result = parse_url($video['url']);
        $request_path = rawurldecode($parse_result["path"]);
        $sign = md5(sprintf("%s-%d-%d-%d-%s", $request_path, $expire_time, $rand_value, $this->uid, $this->privateKey));
        $wait = sprintf("%d-%d-%d-%s", $expire_time, $rand_value, $this->uid, $sign);
        $result = $video['url'] . "?auth_key=" . $wait;
        return $result;
    }

    public function download($fileID)
    {
        $path = '/api/v1/file/download_info';
        $getData = [
            'fileId' => $fileID
        ];
        $responseArray = $this->execute($path, $getData, []);
        return $responseArray['data'];
    }
}